package com.haibara.codesubmiteval.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.haibara.codesubmiteval.chain.userChain.CreateUserDefaultValueHandler;
import com.haibara.codesubmiteval.common.ErrorCode;
import com.haibara.codesubmiteval.constant.AuthConstant;
import com.haibara.codesubmiteval.constant.CommonConstant;
import com.haibara.codesubmiteval.constant.RedisKeyConstant;
import com.haibara.codesubmiteval.context.UserContext;
import com.haibara.codesubmiteval.exception.BusinessException;
import com.haibara.codesubmiteval.exception.ThrowUtils;
import com.haibara.codesubmiteval.mapper.QuestionMapper;
import com.haibara.codesubmiteval.mapper.UserMapper;
import com.haibara.codesubmiteval.model.dto.user.UserAddRequest;
import com.haibara.codesubmiteval.model.dto.user.UserChangePasswordRequest;
import com.haibara.codesubmiteval.model.dto.user.UserQueryRequest;
import com.haibara.codesubmiteval.model.dto.user.UserUpdateRequest;
import com.haibara.codesubmiteval.model.entity.Question;
import com.haibara.codesubmiteval.model.entity.User;
import com.haibara.codesubmiteval.model.vo.*;
import com.haibara.codesubmiteval.service.UserService;
import com.haibara.codesubmiteval.utils.RedisRecoveryUtil;
import com.haibara.codesubmiteval.utils.SqlUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static com.haibara.codesubmiteval.constant.UserConstant.SALT;
import static com.haibara.codesubmiteval.constant.UserConstant.USER_LOGIN_STATE;

/**
 * 用户服务实现
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedisRecoveryUtil redisRecoveryUtil;
    @Resource
    private UserMapper userMapper;
    @Resource
    private CreateUserDefaultValueHandler createUserDefaultValueHandlerHeader;
    @Resource
    private Top10Service top10Service;
    @Resource
    private QuestionMapper questionMapper;

    /**
     * 用户登录
     *
     * @param id       学号
     * @param password 密码
     * @param request  请求
     * @param response 响应
     * @return 用户脱敏信息
     */
    @Override
    public LoginUserVO userLogin(Long id, String password, HttpServletRequest request, HttpServletResponse response) {
        // 1. 加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + password).getBytes());
        User user = this.getBaseMapper().selectById(id);
        ThrowUtils.throwIf(user == null, ErrorCode.PARAMS_ERROR, "用户不存在");
        ThrowUtils.throwIf(!user.getPassword().equals(encryptPassword), ErrorCode.PARAMS_ERROR, "密码错误");
        // 2. 记录用户的登录态
        HttpSession session = request.getSession();
        session.setAttribute(USER_LOGIN_STATE, user.getId());
        session.setMaxInactiveInterval(7 * 24 * 60 * 60);
        return LoginUserVO.getLoginUserVOByUser(user);
    }

    /**
     * 获取当前登录用户
     *
     * @param request 请求
     * @return 用户信息
     */
    @Override
    public User getLoginUser(HttpServletRequest request) {
        // 先判断是否已登录
        HttpSession session = request.getSession(false);
        if (session == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        Object userId = session.getAttribute(USER_LOGIN_STATE);
        // 用户未登录
        if (userId == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        Long id = (Long) userId;
        String userStr = stringRedisTemplate.opsForValue().get(RedisKeyConstant.getCompleteKey(RedisKeyConstant.userKey, id.toString()));
        if (userStr == null || StrUtil.isBlankIfStr(userStr)) {
            // Redis修复
            userStr = redisRecoveryUtil.recoveryUser(id);
        }
        User currentUser;
        if (userStr == null || StrUtil.isBlankIfStr(userStr)) {
            currentUser = userMapper.selectById(id);
            if (currentUser == null || currentUser.getId() == null) {
                // 数据库中不存在该用户信息
                throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
            }
        } else {
            currentUser = JSONUtil.toBean(userStr, User.class);
        }
        // 续期
        session.setMaxInactiveInterval(30 * 24 * 60 * 60);
        return currentUser;
    }

    /**
     * 用户注销
     *
     * @param request 请求
     */
    @Override
    public boolean userLogout(HttpServletRequest request) {
        if (request.getSession().getAttribute(USER_LOGIN_STATE) == null) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");
        }
        // 移除登录态
        UserContext.remove();
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        request.getSession().invalidate();
        return true;
    }

    @Override
    @Transactional
    public Long addUser(UserAddRequest userAddRequest) {
        Long id = userAddRequest.getId();
        String userRole = userAddRequest.getUserRole();
        Integer score = userAddRequest.getScore();
        Set<Long> acceptedQuestionIds = userAddRequest.getAcceptedQuestionIds();
        // 根据新增请求信息创建User对象
        User user = new User();
        user.setId(id);
        user.setUserRole(userRole);
        createUserDefaultValueHandlerHeader.handleDefaultValue(user, userAddRequest);
        userMapper.insert(user);
        // Redis新增排行榜
        top10Service.addOrUpdateTop10(user.getId(), score);
        // 更新Redis AdminIds
        if (user.getUserRole().equals(AuthConstant.USER)) {
            stringRedisTemplate.opsForSet().add(RedisKeyConstant.commonUserIds, user.getId().toString());
        }
        return id;
    }

    @Override
    @Transactional
    public void updateUser(UserUpdateRequest userUpdateRequest) {
        User user = new User();
        User originUser = this.getById(userUpdateRequest.getId());
        BeanUtils.copyProperties(userUpdateRequest, user);
        // 加密密码
        if (!StrUtil.isBlankIfStr(userUpdateRequest.getPassword())) {
            user.setPassword(DigestUtils.md5DigestAsHex((SALT + userUpdateRequest.getPassword()).getBytes()));
        }
        Integer score = userUpdateRequest.getScore();
        if (score != null && !score.equals(originUser.getScore())) {
            // 修改Top10榜单
            top10Service.addOrUpdateTop10(user.getId(), score);
        }
        Set<Long> originSet = new HashSet<>(JSONUtil.toList(originUser.getAcceptedQuestionIds(), Long.class));
        if (userUpdateRequest.getAcceptedQuestionIds() != null && !userUpdateRequest.getAcceptedQuestionIds().isEmpty()) {
            if (
                    originSet.size() != userUpdateRequest.getAcceptedQuestionIds().size() ||
                            !originSet.containsAll(userUpdateRequest.getAcceptedQuestionIds())
            ) {
                // 更新题目通过id集合
                String key = RedisKeyConstant.getCompleteKey(RedisKeyConstant.userAcceptedQuestionIdsSet, user.getId().toString());
                Set<Long> acceptedQuestionIds = userUpdateRequest.getAcceptedQuestionIds();
                stringRedisTemplate.delete(key);
                for (Long acceptedQuestionId : acceptedQuestionIds) {
                    stringRedisTemplate.opsForSet().add(key, acceptedQuestionId.toString());
                }
                // 插入数据库
                user.setAcceptedQuestionIds(JSONUtil.toJsonStr(acceptedQuestionIds));
            }
        }
        userMapper.updateById(user);
        // 清除Redis中User缓存
        if (stringRedisTemplate.hasKey(RedisKeyConstant.getCompleteKey(RedisKeyConstant.userKey, user.getId().toString()))) {
            stringRedisTemplate.delete(RedisKeyConstant.getCompleteKey(RedisKeyConstant.userKey, user.getId().toString()));
        }
        // 修改Redis AdminIds
        if (!originUser.getUserRole().equals(user.getUserRole())) {
            if (originUser.getUserRole().equals(AuthConstant.USER)) {
                stringRedisTemplate.opsForSet().remove(
                        RedisKeyConstant.commonUserIds,
                        originUser.getId().toString()
                );
            } else if (user.getUserRole().equals(AuthConstant.USER)) {
                // 新增入AdminIds
                stringRedisTemplate.opsForSet().add(
                        RedisKeyConstant.commonUserIds,
                        originUser.getId().toString()
                );
            }
        }
    }

    @Override
    @Transactional
    public Integer addUserByFile(MultipartFile file) {
        List<String> lines = new ArrayList<>();
        // 从文件中获取按行数据
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                lines.add(line);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // 批量新增用户
        List<User> users = new ArrayList<>();
        for (String line : lines) {
            User user = new User();
            String[] userInfo = line.split("\t");
            Long userId = Long.valueOf(userInfo[0]);
            String username = userInfo[1];
            user.setId(userId);
            user.setUsername(username);
            user.setScore(0);
            String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userInfo[0]).getBytes());
            user.setPassword(encryptPassword);
            // 班级为学号的 3~8 位
            user.setClazz(userInfo[0].substring(2, 8));
            user.setUserRole(AuthConstant.USER);
            user.setAcceptedQuestionIds(JSONUtil.toJsonStr(CommonConstant.getNewSetWith0()));
            users.add(user);
        }
        this.saveBatch(users);
        // 操作Redis
        for (User user : users) {
            // 题目完成id集合
            stringRedisTemplate.opsForSet().add(
                    RedisKeyConstant.getCompleteKey(RedisKeyConstant.userAcceptedQuestionIdsSet, user.getId().toString()),
                    "0"
            );
            // top10
            top10Service.addOrUpdateTop10(user.getId(), 0);
        }
        log.info("批量新增用户成功");
        return users.size();
    }

    @Override
    public UserAcceptedQuestionVO getUserCompletionOfTheQuestion(Long userId) {
        ThrowUtils.throwIf(userId == null, ErrorCode.NOT_LOGIN_ERROR, "用户未登录");
        Long acceptedNum = stringRedisTemplate.opsForSet().size(
                RedisKeyConstant.getCompleteKey(
                        RedisKeyConstant.userAcceptedQuestionIdsSet, userId.toString()
                )
        );
        if (acceptedNum == null) {
            // 修复缓存
            acceptedNum = (long) redisRecoveryUtil.recoveryUserQuestionSet(userId).size();
        }
        // 因为在创建用户时，在用户通过题目ids集合中填充了0，所以通过题目数应该 -1
        acceptedNum--;
        String questionNumStr = stringRedisTemplate.opsForValue().get(RedisKeyConstant.questionNumber);
        if (questionNumStr == null) {
            questionNumStr = redisRecoveryUtil.recoveryQuestionNum();
        }
        Long questionNum = Long.parseLong(questionNumStr);
        return UserAcceptedQuestionVO.builder()
                .acceptedQuestionNumber(acceptedNum)
                .remainingQuestionNumber(questionNum - acceptedNum)
                .build();
    }

    @Override
    public List<Top10VO> getTop10Ranking() {
        List<Top10VO> top10Ranking = top10Service.getTop10Ranking();
        for (Top10VO top10VO : top10Ranking) {
            // 获取完成题目数
            Long size = stringRedisTemplate.opsForSet().size(
                    RedisKeyConstant.getCompleteKey(
                            RedisKeyConstant.userAcceptedQuestionIdsSet, top10VO.getUserId().toString()
                    )
            );
            if (size == null) {
                // 修复缓存
                size = (long) redisRecoveryUtil.recoveryUserQuestionSet(top10VO.getUserId()).size();
            }
            top10VO.setAcceptedQuestionNumber(size - 1);
        }
        return top10Ranking;
    }

    @Override
    public Top10VO getMyRank(Long userId) {
        ThrowUtils.throwIf(userId == null, ErrorCode.NOT_LOGIN_ERROR);
        Boolean isUser = stringRedisTemplate.opsForSet().isMember(RedisKeyConstant.commonUserIds, userId.toString());
        if (Boolean.FALSE.equals(isUser)) {
            // 管理员不参与排名
            return Top10VO.builder()
                    .userId(userId)
                    .rank(-1)
                    .score(0)
                    .build();
        }
        Long rank = top10Service.getUserRank(userId);
        Integer score = top10Service.getUserScore(userId);
        return Top10VO.builder()
                .userId(userId)
                .rank(Math.toIntExact(rank))
                .score(score)
                .build();
    }

    @Override
    public void changePassword(UserChangePasswordRequest userChangePasswordRequest) {
        String oldPassword = userChangePasswordRequest.getOldPassword();
        String newPassword = userChangePasswordRequest.getNewPassword();
        String ensurePassword = userChangePasswordRequest.getEnsurePassword();
        Long userId = UserContext.get().getId();
        User user = this.getBaseMapper().selectById(userId);
        // 判断旧密码是否正确
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + oldPassword).getBytes());
        ThrowUtils.throwIf(!encryptPassword.equals(user.getPassword()), ErrorCode.PARAMS_ERROR, "旧密码不正确");
        // 判断新密码和确认密码是否相同
        ThrowUtils.throwIf(!newPassword.equals(ensurePassword), ErrorCode.PARAMS_ERROR, "新密码与确认密码不同");
        // 修改密码
        User updateUser = new User();
        updateUser.setId(userId);
        encryptPassword = DigestUtils.md5DigestAsHex((SALT + newPassword).getBytes());
        updateUser.setPassword(encryptPassword);
        userMapper.updateById(updateUser);
    }

    @Override
    public TotalInfo getTotalInfo() {
        // 1. 获取普通用户id列表（总人数）
        Set<String> normalUserIds = stringRedisTemplate.opsForSet().members(RedisKeyConstant.commonUserIds);
        if (normalUserIds == null || normalUserIds.isEmpty()) {
            normalUserIds = redisRecoveryUtil.recoveryCommonUserIds();
        }
        ThrowUtils.throwIf(CollectionUtils.isEmpty(normalUserIds), ErrorCode.SYSTEM_ERROR, "无普通用户");
        int userNumber = normalUserIds.size();
        // 2. 获取总题目通过数
        long totalQuestionAcceptedNumber = batchGetAcceptedCount(normalUserIds) - normalUserIds.size();
        // 3. 获取总分数
        long totalScore = top10Service.getTotalScore();
        // 4. 返回结果
        return TotalInfo.builder()
                .averageScore(totalScore / userNumber)
                .averageAcceptedQuestionNumber(totalQuestionAcceptedNumber / userNumber)
                .build();
    }

    public Long batchGetAcceptedCount(Set<String> userIds) {
        String script = """
                        local total = 0
                        for _, userId in ipairs(KEYS) do
                            local key = "UserQuestionSet:" .. userId
                                total = total + redis.call("SCARD", key)
                        end
                        return total
                """;
        return stringRedisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                new ArrayList<>(userIds)
        );
    }


    @Override
    public List<Long> getMyAcceptedQuestion(Long userId) {
        // 获取用户通过题目id集合
        Set<String> members = stringRedisTemplate.opsForSet().members(
                RedisKeyConstant.getCompleteKey(
                        RedisKeyConstant.userAcceptedQuestionIdsSet,
                        userId.toString()
                )
        );
        if (members == null || members.isEmpty()) {
            members = redisRecoveryUtil.recoveryUserQuestionSet(userId);
        }
        return members.stream().map(Long::parseLong).toList();
    }

    @Override
    public void deleteUser(Long id) {
        this.removeById(id);
        // 删除Redis中该用户的完成题目集合
        stringRedisTemplate.delete(RedisKeyConstant.getCompleteKey(RedisKeyConstant.userAcceptedQuestionIdsSet, id.toString()));
        // 删除Redis排行榜对应信息
        top10Service.removeUser(id);
        // 删除用户缓存
        stringRedisTemplate.delete(RedisKeyConstant.getCompleteKey(RedisKeyConstant.userKey, id.toString()));
        // 删除Redis AdminIds
        if (Boolean.TRUE.equals(stringRedisTemplate.opsForSet().isMember(
                RedisKeyConstant.commonUserIds,
                id.toString()
        ))) {
            // 删除
            stringRedisTemplate.opsForSet().remove(
                    RedisKeyConstant.commonUserIds,
                    id.toString()
            );
        }
    }

    @Override
    public void cleanCommonUser() {
        userMapper.delete(
                new LambdaQueryWrapper<User>()
                        .eq(User::getUserRole, AuthConstant.USER)
        );
    }

    @Override
    public void exportScore() {
        // 1.找出普通学生的user信息
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.eq(User::getUserRole, AuthConstant.USER)
                .orderByAsc(User::getId);
        List<User> users = userMapper.selectList(lqw);
        // 2.构建ExportVO
        List<ExportVO> exportVOList = users.stream().map(
                u -> {
                    String acceptedQuestionIds = u.getAcceptedQuestionIds();
                    Set<Long> ids = new HashSet<>(JSONUtil.toList(
                            acceptedQuestionIds, Long.class
                    ));
                    // 删除默认占位符0
                    ids.remove(0L);
                    if (ids.isEmpty()) {
                        return ExportVO.builder()
                                .id(u.getId().toString())
                                .questionTitles("")
                                .username(u.getUsername())
                                .score(0)
                                .build();
                    }
                    List<String> idString = ids.stream().map(Object::toString).toList();
                    return ExportVO.builder()
                            .id(u.getId().toString())
                            .questionTitles(String.join("，", idString))
                            .username(u.getUsername())
                            .score(u.getScore())
                            .build();
                }
        ).toList();
        // 3.整理题目信息
        List<Question> questions = questionMapper.selectList(null);
        List<ExportQuestionVO> exportQuestionVOS = questions.stream().map(q -> ExportQuestionVO.builder()
                .id(q.getId())
                .title(q.getTitle())
                .score(q.getScore())
                .build()).toList();
        // 4.导出到Excel
        File file = new File("学生成绩.xlsx");
        try (ExcelWriter excelWriter = EasyExcel.write(file).build()) {
            // 第一个 Sheet
            WriteSheet sheet1 = EasyExcel.writerSheet("学生成绩").head(ExportVO.class).build();
            excelWriter.write(exportVOList, sheet1);
            // 第二个 Sheet
            WriteSheet sheet2 = EasyExcel.writerSheet("题目信息").head(ExportQuestionVO.class).build();
            excelWriter.write(exportQuestionVOS, sheet2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Page<User> pageQueryUser(UserQueryRequest userQueryRequest) {
        Long id = userQueryRequest.getId();
        String username = userQueryRequest.getUsername();
        String clazz = userQueryRequest.getClazz();
        String userRole = userQueryRequest.getUserRole();
        long current = userQueryRequest.getCurrent();
        long pageSize = userQueryRequest.getPageSize();
        String sortField = userQueryRequest.getSortField();
        String sortOrder = userQueryRequest.getSortOrder();
        // 构建查询条件
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        // 过滤
        lqw.eq(id != null, User::getId, id)
                .like(!StrUtil.isBlankIfStr(username), User::getUsername, username)
                .like(!StrUtil.isBlankIfStr(clazz), User::getClazz, clazz)
                .eq(!StrUtil.isBlankIfStr(userRole), User::getUserRole, userRole);
        // 排序
        if (SqlUtils.validSortField(sortField)) {
            // 根据用户创建时间排序
            lqw.orderBy("createTime".equals(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), User::getCreateTime);
            // 根据用户id排序
            lqw.orderBy("id".equals(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), User::getId);
            lqw.orderBy("score".equals(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), User::getScore);
        }
        // 分页
        return this.page(new Page<>(current, pageSize), lqw);
    }
}
